<!DOCTYPE html>


<html lang="zh-CN">
  

    <head>
      <meta charset="utf-8" />
        
      <meta name="description" content="欢迎与我交流自动化测试、测试开发、持续集成、DevOps方面知识经验，可在评论区留言。" />
      
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1"
      />
      <title>使用JMeter测试服务器基准性能 |  恰得福来的博客</title>
  <meta name="generator" content="hexo-theme-ayer">
      
      <link rel="shortcut icon" href="/favicon.ico" />
       
<link rel="stylesheet" href="/dist/main.css">

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css"
      />
      
<link rel="stylesheet" href="/css/custom.css">
 
      <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
       
 

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@5.0.1/bulma.min.css"
      />
      <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.19/dist/sweetalert2.min.js"></script>

      <!-- mermaid -->
      
      <style>
        .swal2-styled.swal2-confirm {
          font-size: 1.6rem;
        }
      </style>
    <link rel="alternate" href="/atom.xml" title="恰得福来的博客" type="application/atom+xml">
</head>
  </html>
</html>


<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-使用JMeter测试服务器基准性能"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  使用JMeter测试服务器基准性能
</h1>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2021/09/18/%E4%BD%BF%E7%94%A8JMeter%E6%B5%8B%E8%AF%95%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%9F%BA%E5%87%86%E6%80%A7%E8%83%BD/" class="article-date">
  <time datetime="2021-09-18T13:03:00.000Z" itemprop="datePublished">2021-09-18</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/jmeter/">jmeter</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">4.5k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">18 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>通过对服务器进行基准测试（通常所说的benchmark测试），可得到服务器的基准性能。对于运行web服务的服务器而言，它的基准性能值让我们得以了解服务器本身所能承受的最大并发压力，是任何一个部署到该服务器的web服务的理论并发上限。有些设计良好的web服务能达到或接近这样的数值，而有些则不能。总之，基准能力的高低，不仅涉及到web服务本身的并发能力设计水平，也与服务器的软硬件资源配置和优化设置相关。</p>
<p>本文使用JMeter和其他性能测试工具，对一台运行NGINX服务的8C16G配置的标准Linux虚拟机进行基准性能测试，并尝试对NGINX和服务器本身进行调优，以使其发挥最大并发处理能力。本次使用NGINX作为被测web服务，不涉及数据库操纵或其他处理逻辑，只是简单返回响应消息，这样可以得到一个最大的并发处理数据，我们使用QPS即每秒处理请求数（quest per second）表示。</p>
<h2 id="准备被测环境"><a href="#准备被测环境" class="headerlink" title="准备被测环境"></a>准备被测环境</h2><p>准备两台负载机器用于产生压力，另外准备一台服务器作为被测机器。三台机器都位于同一台物理机器上，每台机器配置都是8C16GB的设置。</p>
<p>我们是使用JMeter执行分布式测试，所以自然在两台测试机器上安装好JMeter，并配置好分布式执行环境。其中一台机器即作为主控节点也作为工作节点。具体怎么配置分布式环境见另外一篇文章<a target="_blank" rel="noopener" href="https://blog.csdn.net/ceo158/article/details/120357653">搭建JMeter分布式测试环境</a>。在被测服务器上安装必备的基础软件，包括jdk、python等，具体安装方法网上自查。其中jdk是运行JMeter所必备，python主要是用来运行用于统计数据的脚本。</p>
<h3 id="被测服务"><a href="#被测服务" class="headerlink" title="被测服务"></a>被测服务</h3><p>使用NGINX作为被测的web服务器。NGINX是一个高性能的web服务或反向代理服务器，性能非常强劲。根据NGINX官方的测试报告<a target="_blank" rel="noopener" href="https://www.nginx.com/blog/testing-the-performance-of-nginx-and-nginx-plus-web-servers/">戳这里</a>，NGINX在一台8C16G的物理服务器（所谓裸金属服务器）的基准性能是，当并发连接为400个、且响应返回的文件大小是1KB的时候，QPS可以达到26w多，非常的高。</p>
<p>我们本次实验使用的机器是虚拟机，CPU品牌和NGINX官网上那篇文章使用的基本一样，都是<code>Intel至强处理器</code>，但是具体型号有差异，CPU频率略低一些。所以我的测试目的是能够尽量接近官方的这个QPS数值。</p>
<h2 id="测试工具"><a href="#测试工具" class="headerlink" title="测试工具"></a>测试工具</h2><p>使用的测试工具包括ab、wrk、jmeter。其中ab、wrk都是作为jmeter的对照物。也可以使用其他工具，比如gatling对比一下。此次使用ab、wrk互为印证就够了。</p>
<p>使用apache benchmark（简称ab）和wrk执行最基础的测试，用来作为与JMeter分布式测试的效果的对比。有对比才有高下之分对吧。ab和wrk纯粹作为互相参照印证的工具。</p>
<p>1）安装ab</p>
<p>在CentOS上安装ab，只需要一行命令：<code>yum install /usr/bin/ab</code>。安装好以后在命令行执行<code>ab</code>验证是否成功。</p>
<p>2）安装wrk</p>
<p>先安装git，因为make的时候需要用到。下载git工程后安装，工程地址<a target="_blank" rel="noopener" href="https://github.com/wg/wrk">https://github.com/wg/wrk</a>。下载后进入wrk的git工程目录直接执行make，得到一个二进制文件wrk，这个就是可执行程序。</p>
<p>3）安装jmeter</p>
<p>在<a target="_blank" rel="noopener" href="https://jmeter.apache.org/download_jmeter.cgi">jmeter官网</a>下载解压即可。部署分布式环境请参考另外一篇文章<a href="https://johnny1952.github.io/2021/09/16/%E6%90%AD%E5%BB%BAJMeter%E5%88%86%E5%B8%83%E5%BC%8F%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83/">搭建JMeter分布式测试环境</a>。</p>
<h2 id="服务器和NGINX服务优化"><a href="#服务器和NGINX服务优化" class="headerlink" title="服务器和NGINX服务优化"></a>服务器和NGINX服务优化</h2><ol>
<li>对负载机和被测服务器均进行相关优化，具体优化可参考我的另外一篇博客<a href="https://johnny1952.github.io/2021/10/05/CentOS7%E8%B0%83%E4%BC%98%E5%AE%9E%E8%B7%B5/">CentOS7调优实践</a>。</li>
<li>对NGINX服务进行设置优化：<br>需要优化的配置包括如下（更多见具体nginx.conf文件中的设置）：</li>
</ol>
<ul>
<li>worker_rlimit_nofile：为nginx的work进程所运行的用户（一般是nginx）设置可打开的最大文件数</li>
<li>worker_connections：最大访问客户数,修改此值时,不能超过 worker_rlimit_nofile 值</li>
<li>worker_processes：worker进程数，一般设为auto即可，也可以设为等于或超过cpu数的数量，不宜超过太多</li>
<li>worker_cpu_affinity：cpu亲和性</li>
<li>keepalive_requests：这个还没配过，默认是1000。这个配置项是nginx对于单个keepalive连接可处理的请求的数量。低于1.19.10的版本默认是100. 关于这点有人也做了探索，见 <a target="_blank" rel="noopener" href="https://github.com/jinhailang/blog/issues/37">https://github.com/jinhailang/blog/issues/37</a>。因为我测试的时候每个连接都只发100个请求，所以默认值够用了。但是如果一个请求要发送很多请求，比如长时间的压力测试，那么这个值可能要设置的比较大一些好点。</li>
<li>keepalive_timeout：空闲保活连接保持打开状态的时间。默认是75s。默认值即可。</li>
<li>access_log：设置日志路径和级别</li>
</ul>
<p>nginx.conf完整配置见附录<a href="#nginx%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%A4%BA%E4%BE%8B">nginx配置文件示例</a>。</p>
<h2 id="执行测试"><a href="#执行测试" class="headerlink" title="执行测试"></a>执行测试</h2><h3 id="使用ab执行测试"><a href="#使用ab执行测试" class="headerlink" title="使用ab执行测试"></a>使用ab执行测试</h3><p>压测nginx的主页地址，nginx将返回一段大小不到1KB的文本。</p>
<p>1）用例1：单台负载机启动8个ab进程使用大连接数进行测试</p>
<p>单机启动8个ab进程压测，qps大概可以达到2.6w左右。负载机cpu和被测服务器的cpu利用都大概50%左右。</p>
<p>具体测试命令是：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 通过脚本多进程压测，根据cpu数启动相应个数的ab进程。每个并发60000，实现6k*8=4.8w个并发</span></span><br><span class="line"><span class="comment">#!/bin/bash</span></span><br><span class="line">rm -rf *.<span class="built_in">log</span></span><br><span class="line"><span class="keyword">for</span>((i=1;i&lt;=8;i++));</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">  nohup ab -k -r -n 600000 -c 6000 <span class="string">&quot;http://192.168.1.182:80/&quot;</span>  &gt; request<span class="variable">$&#123;i&#125;</span>.<span class="built_in">log</span> 2&gt;&amp;1 &amp;</span><br><span class="line">  sleep 1</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure>

<p>下面是查看8个ab进程的测试结果，每个进程的qps综合相加就是总的qps：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">[root@hadoop0 performance_test]<span class="comment"># cat request1.log  | grep &quot;Requests per second:&quot;</span></span><br><span class="line">Requests per second:    4025.82 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">[root@hadoop0 performance_test]<span class="comment"># cat request2.log  | grep &quot;Requests per second:&quot;</span></span><br><span class="line">Requests per second:    3097.47 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">[root@hadoop0 performance_test]<span class="comment"># cat request3.log  | grep &quot;Requests per second:&quot;</span></span><br><span class="line">Requests per second:    3266.06 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">[root@hadoop0 performance_test]<span class="comment"># cat request4.log  | grep &quot;Requests per second:&quot;</span></span><br><span class="line">Requests per second:    3284.48 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">[root@hadoop0 performance_test]<span class="comment"># cat request5.log  | grep &quot;Requests per second:&quot;</span></span><br><span class="line">Requests per second:    3338.12 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">[root@hadoop0 performance_test]<span class="comment"># cat request6.log  | grep &quot;Requests per second:&quot;</span></span><br><span class="line">Requests per second:    3120.59 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">[root@hadoop0 performance_test]<span class="comment"># cat request7.log  | grep &quot;Requests per second:&quot;</span></span><br><span class="line">Requests per second:    3127.17 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">[root@hadoop0 performance_test]<span class="comment"># cat request8.log  | grep &quot;Requests per second:&quot;</span></span><br><span class="line">Requests per second:    3156.94 [<span class="comment">#/sec] (mean)</span></span><br></pre></td></tr></table></figure>

<p>2）用例2：2台负载机每台启动8个ab进程使用大连接数进行测试</p>
<p>同时使用两台负载机进行压测。每台启动8个ab进程，并发约4.8w（在可允许的端口数范围内），两台机器理论可以启动约9.6万ab进程压测。测试命令与用例1相同。不过还是没有能使被测服务器达到90%以上cpu利用率。</p>
<p>经过多次测试，两台机器得到总qps是接近3w，相比单台机器压测，并没有多大提升：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[root@hadoop0 performance_test]<span class="comment"># python ./calculate_ab_qps.py</span></span><br><span class="line">(<span class="string">&#x27;total qps is : &#x27;</span>, 16312.919999999998)</span><br><span class="line">[root@hadoop1 performance_test]<span class="comment"># python ./calculate_ab_qps.py</span></span><br><span class="line">(<span class="string">&#x27;total qps is : &#x27;</span>, 13512.26)</span><br></pre></td></tr></table></figure>

<blockquote>
<p>calculate_ab_qps.py是一个汇总计算各request.log中qps数据并汇总的python小工具。</p>
</blockquote>
<p>结论是：在大连接数量的情况，通过ab测试得到的被测NGINX服务器的性能大约就是3w。</p>
<p>3）用例3: 2台压力负载机器均使用少连接数进行测试</p>
<p>作为对比，使用较少的连接数进行测试。此种情况下，被测服务器的CPU利用率应比大连接时更高。</p>
<p>测试命令如下：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 通过脚本多进程压测，根据cpu数启动相应个数的ab进程。每个并发50，实现50*8=400个并发</span></span><br><span class="line"><span class="comment">#!/bin/bash</span></span><br><span class="line">rm -rf *.<span class="built_in">log</span></span><br><span class="line"><span class="keyword">for</span>((i=1;i&lt;=8;i++));</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">  nohup ab -k -r -n 600000 -c 50 <span class="string">&quot;http://192.168.1.182:80/&quot;</span>  &gt; request<span class="variable">$&#123;i&#125;</span>.<span class="built_in">log</span> 2&gt;&amp;1 &amp;</span><br><span class="line">  sleep 1</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure>

<p>两台负载机器测试得到的QPS大约相等，差不多都是4.8w，相加总和是9.6w，所以被测NGINX的QPS是9.6w左右。资源消耗方面，被测服务器CPU利用率可以达到80%左右，网络负载在700Mbps左右，大约是70%的利用率。此时服务器资源看起来还是有一定的余量。然而压测端CPU利用率没有超过50%，包括内存、网络等都没有到瓶颈，似乎可以增加更多压力。有机会可以再进行探索测试，比如尝试启用更多ab进程，比如设置进程数为CPU数的150%。</p>
<h3 id="使用wrk执行测试"><a href="#使用wrk执行测试" class="headerlink" title="使用wrk执行测试"></a>使用wrk执行测试</h3><p>1）用例1: 低连接数模式</p>
<p>两台负载机每台以较低数量的连接数，保持keepalive，对服务器发送请求。<br>具体测试命令如下，每台机器使用8个wrk进程，每个进程也都设置为单线程，这样效率最高：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> `seq 0 7`; <span class="keyword">do</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;worker <span class="variable">$i</span>\n&quot;</span></span><br><span class="line">    taskset -c <span class="variable">$i</span> /opt/wrk/wrk -t 1 -c 50 -d 180s http://192.168.1.182:80/ &amp;</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure>

<p>每台机器测试得到的结果qps大约5w，两台的总和就是10万。现在终于把这台被测机器的极限处理性能给测试出来了！被测服务器cpu使用率压到接近100%，已经不能再提上去了，因为NGINX所在服务器的cpu现在已经是瓶颈了！除非提升硬件配置。</p>
<p>2）用例2: 高连接数模式</p>
<p>换一种测试方式。上面是测试较少连接（connection）的情况下NGINX服务器的处理性能。接下来尝试大并发连接的情况下NGINX的处理性能。</p>
<p>每个进程起7000个连接，一台机器则连接总数总共是5.6w，不超过可用端口总数（大约是6w左右），命令如下：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> `seq 0 7`; <span class="keyword">do</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;worker <span class="variable">$i</span>\n&quot;</span></span><br><span class="line">    taskset -c <span class="variable">$i</span> /opt/wrk/wrk -t 1 -c 7000 -d 300s http://192.168.1.182:80/ &amp;</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure>

<p>这种情况下，服务器的cpu利用率没有上一种模式那么高，大约是50～60%，网络带宽下降了接近一半。2台负载机器得到总的服务器的qps处理能力是3.7w左右，下降非常多。所以多连接情况下，NGINX处理效率没有那么高，资源利用率也相对低一些。</p>
<p>对比wrk和ab，似乎wrk可以产生更大的压力。</p>
<h3 id="使用jmeter执行测试"><a href="#使用jmeter执行测试" class="headerlink" title="使用jmeter执行测试"></a>使用jmeter执行测试</h3><p>上面使用ab和wrk测试的结果都已经出来，可以看到得到数据是类似的。现在可以使用ab和wrk测试得到qps作为被测服务的性能基准，可以认为较低并发（1k）的情况下被测服务的QPS能力约9.8w，较高并发（10w）情况下被测服务的QPS能力约3.3w。接下来再使用jmeter进行性能测试。</p>
<p>首先确认对jmeter进行了适当的优化，包括jmeter-server服务和jmter脚本，具体优化措施参考我另外一篇博文。</p>
<p>1）用例1： 高连接数，使用2台jmeter-server测试，每台创建1w个线程发送请求</p>
<p>在使用jmeter进行性能测试时，关闭所有断言，可以得到更好的测试效果。如果为了分析被测服务逻辑处理的正确率，可以在事后进行统计分析。</p>
<p>使用两台机器启动分布式命令，一台是192.168.1.181，一台是本机：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./jmeter -n -t test_nginx_benchmark.jmx -l test_nginx_benchmark.jtl -R192.168.1.181,localhost  -e -o test_nginx_benchmark -f</span><br></pre></td></tr></table></figure>

<p>性能在2.5w左右，而被测服务器的cpu利用率目测只有30%左右，效果与ab、wrk是接近的：</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;Total&quot;</span> : &#123;</span><br><span class="line">  <span class="attr">&quot;transaction&quot;</span> : <span class="string">&quot;Total&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;sampleCount&quot;</span> : <span class="number">1000000</span>,</span><br><span class="line">  <span class="attr">&quot;throughput&quot;</span> : <span class="number">25401.986435339244</span>,</span><br><span class="line">  <span class="attr">&quot;receivedKBytesPerSec&quot;</span> : <span class="number">21160.053153656616</span>,</span><br><span class="line">  <span class="attr">&quot;sentKBytesPerSec&quot;</span> : <span class="number">4068.286890034801</span>,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>但是在高连接的情况下，jmeter确实比ab或wrk更消耗资源。1w并发的情况下8核的总cpu利用率达到了80%（按总100%计算）。因为jmeter实际上是对每一个连接都建立了一个线程，所以每台负载机器在执行测试时都产生了约1w个线程。操作系统需要对这1w个线程进行调度，通过观察，CPU资源使用率大约是ab或wrk的2倍。</p>
<p>2）用例2： 低连接数，使用2台jmeter-server测试，每台创建500个线程发送请求</p>
<p>仍然是利用两台jmeter机器压测，但这次采用低连接模式，连接数少得多。每台机器启动一个jmeter-server服务进程，每个服务进程启动500个线程，与ab、wrk测试时一样，每个线程保持http连接keepalive，循环向服务端发送请求，每个线程共计循环约2000次。</p>
<p>这次测试得到的服务器QPS大约在5w。负载服务器的CPU利用率和用例1的情况类似，CPU利用率达到了极限。但服务器的CPU率没有达到极限，估计在50%左右。相比较用例1，由于负载机器的资源利用和工作模式更改，显然对服务器的压力更大了，但服务器显然还有处理余量，此时服务的QPS与使用wrk相比，只有一半左右。根据上述资源分析情况，可以看到jmeter确实比wrk、ab更消耗cpu，效率更低。在此次测试场景中，jmeter和负载机器成为了瓶颈。未来有机会，将尝试对jmeter和运行模式进行调优，降低对CPU的消耗率，使得能以较少的机器产生较多的负载量。</p>
<h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><h3 id="nginx配置文件示例"><a href="#nginx配置文件示例" class="headerlink" title="nginx配置文件示例"></a>nginx配置文件示例</h3><p>/etc/nginx/nginx.conf内容示例：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line">#v1.1:增加cpu亲和力设置;增加keepalive设置</span><br><span class="line">user nginx;</span><br><span class="line">worker_processes 8;</span><br><span class="line">worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;</span><br><span class="line"></span><br><span class="line">error_log /var/log/nginx/error.log crit;</span><br><span class="line">pid /var/run/nginx.pid;</span><br><span class="line"></span><br><span class="line">worker_rlimit_nofile 65535;</span><br><span class="line"></span><br><span class="line">events &#123;</span><br><span class="line">    #worker_connections  1024;</span><br><span class="line">    use epoll;</span><br><span class="line">    multi_accept on;</span><br><span class="line">    #在Nginx接到一个新连接通知后,调用accept()来接受尽量多的连接</span><br><span class="line">    worker_connections 65535;</span><br><span class="line">    #最大访问客户数,修改此值时,不能超过 worker_rlimit_nofile 值</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">http &#123;</span><br><span class="line">    keepalive_timeout  120s 120s;</span><br><span class="line">    keepalive_requests 10000;</span><br><span class="line"></span><br><span class="line">    include /etc/nginx/mime.types;</span><br><span class="line">    default_type application/octet-stream;</span><br><span class="line"></span><br><span class="line">    log_format main</span><br><span class="line">        &#x27;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &#x27;</span><br><span class="line">        &#x27;$status $body_bytes_sent &quot;$http_referer&quot; &#x27;</span><br><span class="line">        &#x27;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&#x27;;</span><br><span class="line"></span><br><span class="line">    access_log /var/log/nginx/access.log main;</span><br><span class="line">    #GZIP性能优化</span><br><span class="line"></span><br><span class="line">    gzip on;</span><br><span class="line">    gzip_min_length 1100;</span><br><span class="line">    #对数据启用压缩的最少字节数,如:请求小于1K文件,不要压缩,压缩小数据会降低处理此请求的所有进程速度</span><br><span class="line">    gzip_buffers 4 16k;</span><br><span class="line">    gzip_proxied any;</span><br><span class="line">    #允许或者禁止压缩基于请求和响应的响应流,若设置为any,将会压缩所有请求</span><br><span class="line">    gzip_http_version 1.0;</span><br><span class="line">    gzip_comp_level 9;</span><br><span class="line">    #gzip压缩等级在0-9内,数值越大压缩率越高,CPU消耗也就越大</span><br><span class="line">    gzip_types text/plain</span><br><span class="line">        text/css</span><br><span class="line">        application/javascript</span><br><span class="line">        application/x-javascript</span><br><span class="line">        text/xml</span><br><span class="line">        application/xml</span><br><span class="line">        application/xml+rss</span><br><span class="line">        text/javascript</span><br><span class="line">        application/json</span><br><span class="line">        image/jpeg</span><br><span class="line">        image/gif</span><br><span class="line">        image/png;</span><br><span class="line">    #压缩类型</span><br><span class="line">    gzip_vary on;</span><br><span class="line">    #varyheader支持,让前端的缓存服务器识别压缩后的文件,代理</span><br><span class="line">    include /usr/local/nginx/conf/vhosts/*.conf;</span><br><span class="line">    #在当前文件中包含另一个文件内容的指令</span><br><span class="line"></span><br><span class="line">    #静态文件的缓存性能调优</span><br><span class="line"></span><br><span class="line">    open_file_cache max=65535 inactive=20s;</span><br><span class="line">    #这个将为打开文件指定缓存,max 指定缓存数量.建议和打开文件数一致.inactive 是指经过多长时间文件没被请求后删除缓存</span><br><span class="line">    open_file_cache_valid 30s;</span><br><span class="line">    #这个是指多长时间检查一次缓存的有效信息,例如我一直访问这个文件,30秒后检查是否更新,反之更新</span><br><span class="line">    open_file_cache_min_uses 2;</span><br><span class="line">    #定义了open_file_cache中指令参数不活动时间期间里最小的文件数</span><br><span class="line">    open_file_cache_errors on;</span><br><span class="line">    #NGINX可以缓存在文件访问期间发生的错误,这需要设置该值才能有效,如果启用错误缓存.则在访问资源（不查找资源）时.NGINX会报告相同的错误</span><br><span class="line"></span><br><span class="line">    sendfile on;</span><br><span class="line">    tcp_nopush on;</span><br><span class="line">    tcp_nodelay on;</span><br><span class="line">    #是否启用 nagle 缓存算法,告诉nginx不要缓存数据</span><br><span class="line"></span><br><span class="line">    #gzip  on;</span><br><span class="line"></span><br><span class="line">    include /etc/nginx/conf.d/*.conf;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="jmeter测试脚本"><a href="#jmeter测试脚本" class="headerlink" title="jmeter测试脚本"></a>jmeter测试脚本</h3><p>jmeter脚本比较简单，不过由于存储格式是xml，略显臃肿，仅贴出脚本截图。</p>
<p><img src="/images/jmeter5/benchmark/nginx-benchmark.png" alt="nginx-benchmark"></p>
<h3 id="统计ab测试结果的python脚本"><a href="#统计ab测试结果的python脚本" class="headerlink" title="统计ab测试结果的python脚本"></a>统计ab测试结果的python脚本</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!coding=utf-8</span></span><br><span class="line"><span class="keyword">import</span> os, sys</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">计算多个ab进程测试的总qps。</span></span><br><span class="line"><span class="string">具体是计算当前目录下所有log中的 &quot;Requests per second:    3156.94 [#/sec] (mean)&quot;中包含的qps， 然后相加</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_qps</span>(<span class="params">text</span>):</span></span><br><span class="line">    pattern = <span class="string">r&#x27;Requests per second:\s*(.*)\s\[#/sec] \(mean\)&#x27;</span></span><br><span class="line">    ret = re.findall(pattern, text)</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(ret) &gt; <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> ret[<span class="number">0</span>]</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>  <span class="comment"># 如果没找到，可能出现了错误，暂时返回0</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">cur_dir = <span class="string">&quot;.&quot;</span></span><br><span class="line">total = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> root, <span class="built_in">dir</span>, files <span class="keyword">in</span> os.walk(cur_dir):</span><br><span class="line">    <span class="keyword">for</span> file <span class="keyword">in</span> files:</span><br><span class="line">        log = os.path.join(root, file)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> log.__contains__(<span class="string">&quot;.log&quot;</span>):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(log, <span class="string">&quot;r&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">            text = f.read()</span><br><span class="line">            qps_text = get_qps(text)</span><br><span class="line">            qps_num = <span class="built_in">float</span>(qps_text)</span><br><span class="line">            total += qps_num</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;total qps is : &quot;</span>, total)</span><br></pre></td></tr></table></figure>

<h3 id="统计wrk测试结果python脚本"><a href="#统计wrk测试结果python脚本" class="headerlink" title="统计wrk测试结果python脚本"></a>统计wrk测试结果python脚本</h3><p>脚本与统计ab的逻辑一直的，模式上略有区别。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!coding=utf-8</span></span><br><span class="line"><span class="keyword">import</span> os, sys</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">计算多个wrk进程测试的总qps。</span></span><br><span class="line"><span class="string">具体是计算当前目录下所有log中的 &quot;Requests/sec:  10099.83&quot;中包含的qps， 然后相加</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_qps</span>(<span class="params">text</span>):</span></span><br><span class="line">    <span class="comment">#pattern = r&#x27;Requests per second:\s*(.*)\s\[#/sec] \(mean\)&#x27;</span></span><br><span class="line">    <span class="comment">#Requests/sec:  10099.83</span></span><br><span class="line">    pattern = <span class="string">r&#x27;Requests/sec:\s*(.*)\s*&#x27;</span></span><br><span class="line">    ret = re.findall(pattern, text)</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(ret) &gt; <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> ret[<span class="number">0</span>]</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>  <span class="comment"># 如果没找到，可能出现了错误，暂时返回0</span></span><br><span class="line"></span><br><span class="line">cur_dir = <span class="string">&quot;.&quot;</span></span><br><span class="line">total = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> root, <span class="built_in">dir</span>, files <span class="keyword">in</span> os.walk(cur_dir):</span><br><span class="line">    <span class="keyword">for</span> file <span class="keyword">in</span> files:</span><br><span class="line">        log = os.path.join(root, file)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> log.__contains__(<span class="string">&quot;.log&quot;</span>):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(log, <span class="string">&quot;r&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">            text = f.read()</span><br><span class="line">            qps_text = get_qps(text)</span><br><span class="line">            qps_num = <span class="built_in">float</span>(qps_text)</span><br><span class="line">            total += qps_num</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;total qps is : &quot;</span>, total)</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>Nginx高并发下的优化: <a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000011405320">https://segmentfault.com/a/1190000011405320</a></li>
<li>CentOS7调优实践：<a href="https://johnny1952.github.io/2021/10/05/CentOS7%E8%B0%83%E4%BC%98%E5%AE%9E%E8%B7%B5/">https://johnny1952.github.io/2021/10/05/CentOS7%E8%B0%83%E4%BC%98%E5%AE%9E%E8%B7%B5/</a></li>
</ul>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://johnny1952.github.io/2021/09/18/%E4%BD%BF%E7%94%A8JMeter%E6%B5%8B%E8%AF%95%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%9F%BA%E5%87%86%E6%80%A7%E8%83%BD/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/jmeter/" rel="tag">jmeter</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/" rel="tag">性能测试</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2021/10/05/CentOS7%E8%B0%83%E4%BC%98%E5%AE%9E%E8%B7%B5/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            CentOS7调优实践
          
        </div>
      </a>
    
    
      <a href="/2021/09/16/%E6%90%AD%E5%BB%BAJMeter%E5%88%86%E5%B8%83%E5%BC%8F%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">搭建JMeter分布式测试环境</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "BDYyK8ADltP2ULGCnAJe5ufP-gzGzoHsz",
    app_key: "yCqG1hemYrFKJahvOBl6uppH",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "你有什么想法或问题？快来评论一下吧!",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
   
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2021
        <i class="ri-heart-fill heart_icon"></i> Johnny Li
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></span>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1280251923&amp;web_id=1280251923'></script>
        
      </li>
    </ul>
  </div>
</footer>    
    </main>
    <div class="float_btns">
      <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

    </div>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="恰得福来的博客"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="https://johnny1952.github.io/gitbook-tutorial">gitbook</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/img/alipay.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/img/wechat.jpg">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-3.6.0.min.js"></script>
 
<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->
 
<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: ".tocbot",
    contentSelector: ".article-entry",
    headingSelector: "h1, h2, h3, h4, h5, h6",
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: "main",
    positionFixedSelector: ".tocbot",
    positionFixedClass: "is-position-fixed",
    fixedSidebarOffset: "auto",
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->
 <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script> 
<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->
 
<script src="/js/busuanzi-2.3.pure.min.js"></script>
 
<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->
 
<link rel="stylesheet" href="/css/clipboard.css">
 <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>
 
<!-- CanvasBackground -->

<script>
  if (window.mermaid) {
    mermaid.initialize({ theme: "forest" });
  }
</script>


    
    

  </div>
</body>

</html>